﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using IEx.Common;

namespace IEx.Downloader
{
    public class DownloadProcess
    {
        private DownloadQueue downloadQueue;
        private IStore store;
#if !DEBUG
        private const int LIMITTED_THREAD = 8;
#else
        private const int LIMITTED_THREAD = 1; // Use 1 thread to debug easily
#endif
        private List<IEx.Model.Partials.Announcement> pickedQueues;
        private Dictionary<int, WaitHandle> waitHandles;
        private static object locked = new object();
        private int totalProcess = 0;
        private int completeProcess = 0;

        public DownloadProcess(DownloadQueue queue, IStore store)
        {
            this.downloadQueue = queue;
            this.store = store;
            this.pickedQueues = new List<IEx.Model.Partials.Announcement>();
            //waitHandles = new WaitHandle[LIMITTED_THREAD];
            waitHandles = new Dictionary<int, WaitHandle>();
        }

        /// <summary>
        /// Fired after all items in queue have proceeded successful
        /// </summary>
        public event FinishEventHandler OnFinish;

        public void Start(bool isNewScanningRound = true)
        {
            if (isNewScanningRound)
            {
                completeProcess = 0;
                totalProcess = 0;
            }
            Logger.Debug("Start downloading ({0} items)", downloadQueue.QueueCount);

            int index = 0;
            while (true)
            {
                if (downloadQueue.QueueCount > 0)
                {
                    totalProcess++;
                    IEx.Model.Partials.Announcement queue = downloadQueue.Dequeue();
                    this.AddThread(index, new ManualResetEvent(false));
                    ThreadPool.QueueUserWorkItem((o) =>
                    {
                        ThreadArgs arg = o as ThreadArgs;
                        if (arg != null)
                        {
                            if (this.store != null && this.store.Save(arg.Item))
                            {
                                completeProcess++;
                            }
                            WaitHandle await = this.GetWaitHandle(arg.Index);
                            if (await != null)
                            {
                                ((ManualResetEvent)await).Set();
                                this.RemoveThread(arg.Index);
                            }

                        }
                    }, new ThreadArgs(index, queue));

                    index++;
                }

                if (this.GetThreadCount() == LIMITTED_THREAD)
                {
                    // Wait while pool is full   
                    while (true)
                    {
                        Thread.Sleep(200);
                        if (this.GetThreadCount() < LIMITTED_THREAD)
                            break;
                    }
                }

                // Wait for the all threads finish
                if (this.GetThreadCount() == 0 && downloadQueue.QueueCount == 0)
                {
                    break;
                }
            }

            Thread.Sleep(1000);
            if (downloadQueue.QueueCount != 0)
            {
                this.Start(false);
                return;
            }

            if (this.OnFinish == null)
                Logger.WriteLogInfo("OnFinish event handler is null");

            if (this.OnFinish != null)
                this.OnFinish.Invoke(new FinishEventArg(totalProcess, completeProcess));
        }

        private int GetThreadCount()
        {
            lock (locked)
            {
                return this.waitHandles.Count;
            }
        }

        private void RemoveThread(int index)
        {
            lock (locked)
            {
                if (this.waitHandles.ContainsKey(index))
                    this.waitHandles.Remove(index);
            }
        }

        private void AddThread(int index, WaitHandle waitHandle)
        {
            lock (locked)
            {
                if (!this.waitHandles.ContainsKey(index))
                    this.waitHandles.Add(index, waitHandle);
            }
        }

        private WaitHandle GetWaitHandle(int index)
        {
            lock (locked)
            {
                if (this.waitHandles.ContainsKey(index))
                    return this.waitHandles[index];
                return null;
            }
        }

        public void StartForever()
        {
            while (true)
            {
                Start();
                System.Diagnostics.Debug.WriteLine("Still starting...");
                Thread.Sleep(2000);
            }
        }

        private class ThreadArgs
        {
            public ThreadArgs(int index, IEx.Model.Partials.Announcement item)
            {
                this.Index = index;
                this.Item = item;
            }
            public int Index
            {
                get;
                private set;
            }
            public IEx.Model.Partials.Announcement Item
            {
                get;
                private set;
            }
        }
    }
}
